import cache from '@/plugins/cache'
import axiosInstance from './index'
import Vue from 'vue'
import TwoFAWindow from '@/components/common/TwoFAWindow'
const requestMethods = ['get', 'post', 'delete', 'put', 'head', 'options', 'patch', 'request']
const extendsMethods = {}

/**
 * 模拟Promise，达到Promise的then，catch和finally可以重复执行
 */
const RepeatablePromise = function (fn) {
  this.__then = []
  this.__catch = null
  this.__finally = null
  this.then = function (thenFn) {
    this.__then.push(thenFn)
    return this
  }
  this.catch = function (catchFn) {
    this.__catch = catchFn
    return this
  }
  this.finally = function (finallyFn) {
    this.__finally = finallyFn
    return this
  }
  fn(data => {
    try {
      let returnData = null
      for (let i = 0; i < this.__then.length; i++) {
        if (i === 0) {
          returnData = this.__then[i](data)
        } else {
          returnData = this.__then[i](returnData)
        }
      }
    } catch (e) {
      if (this.__catch) {
        throw new Error('Eva: ')
      }
      this.__catch && this.__catch(e)
    }
    this.__finally && this.__finally()
  }, e => {
    this.__catch && this.__catch(e)
    this.__finally && this.__finally()
  })
}

/**
 * 构建基础请求对象Promise代理对象
 * @param method
 * @param args
 * @returns {{__access(*, *=): *, finally(): *, then(): *, catch(): *, __result_promise: null, __arguments: *}|*}
 */
const buildBasePromiseProxy = (method, args) => {
  return {
    // post，get等请求方法的调用参数
    __arguments: args,
    // 请求结果的promise对象
    __result_promise: null,
    // 代理then方法，直接调用目标promise的then方法
    then () {
      return this.__access('then', arguments)
    },
    // 代理catch方法，直接调用目标promise的catch方法
    catch () {
      return this.__access('catch', arguments)
    },
    // 代理finally方法，直接调用目标promise的finally方法
    finally () {
      return this.__access('finally', arguments)
    },
    __access (methodName, args) {
      if (this.__result_promise != null) {
        return this.__result_promise[methodName].apply(this.__result_promise, args)
      }
      // 开启了2FA认证
      if (this.__with_2fa) {
        if (this.__2fa_window != null) {
          this.__result_promise = new RepeatablePromise((resolve, reject) => {
            this.__2fa_window.$on('then', resolve)
            this.__2fa_window.$on('catch', reject)
            this.__2fa_window.$on('cancel', reject)
          })
          // - 打开窗口，在此处打开窗口，意味着必须执行then，catch或finally才会打开2fa认证窗口
          this.__2fa_window.open(axiosInstance, method, this.__arguments)
        }
      }
      // 未开启2fa的情况下，直接发送请求
      if (this.__result_promise == null) {
        this.__result_promise = axiosInstance[method].apply(axiosInstance, this.__arguments)
      }
      // 如果开启了缓存，则在请求成功后写入缓存
      if (this.__with_cache) {
        this.__result_promise.then(data => {
          this.__cache_impl.set(this.__cache_key, data)
          return data
        })
      }
      return this.__result_promise[methodName].apply(this.__result_promise, args)
    }
  }
}

/**
 * 构建缓存请求对象Promise代理对象
 * @param cacheKey
 * @param method
 * @param args
 * @param cacheImplName
 * @returns {{cache(): *, __with_cache: boolean, __cache_key: *, __cache_impl: *}|Promise<any>|buildCachePromiseProxy}
 */
const buildCachePromiseProxy = (cacheKey, method, args, cacheImplName) => {
  return {
    // 缓存标记
    __with_cache: true,
    // 缓存key
    __cache_key: cacheKey,
    // 缓存实现
    __cache_impl: cache[cacheImplName],
    // 从缓存中获取数据
    cache () {
      // 从缓存中获取数据
      const data = this.__cache_impl.get(cacheKey)
      // 如果从缓存中获取到了数据，则直接构造一个成功的promise
      if (data != null) {
        this.__result_promise = Promise.resolve(data)
      }
      // 如果已经获取到了数据，则由以上成功的promise来接手then和catch的处理
      if (this.__result_promise != null) {
        return this.__result_promise
      }
      return this
    }
  }
}

/**
 * 构建2FA请求对象Promise代理对象
 * @param twoFAWindow
 * @returns {{__2fa_window: *, __with_2fa: boolean}}
 */
const build2FAPromiseProxy = twoFAWindow => {
  return {
    // 2fa标记
    __with_2fa: true,
    // 2fa窗口对象
    __2fa_window: twoFAWindow
  }
}

/**
 * 扩展方法：开启缓存
 * @param cacheKey 缓存的key
 * @param isLocal 是否缓存到本地缓存LocalStorage，为false时缓存到SessionStorage
 * @usage：request.cache('test').[post(), get()...]
 * @returns {{isExtendsAxiosInstance: boolean, post: Function, get: Function, ...}}
 */
extendsMethods.cache = function (cacheKey, isLocal = false) {
  if (cacheKey == null) {
    throw Error('Request cache key can not be null.')
  }
  let cacheAxiosInstance = {
    // 标记为axios扩展实例，用于与原生axios作区分
    isExtendsAxiosInstance: true
  }
  if (this.isExtendsAxiosInstance) {
    cacheAxiosInstance = this
  }
  for (const method of requestMethods) {
    if (cacheAxiosInstance[method] == null) {
      cacheAxiosInstance[method] = function () {
        return {
          ...buildBasePromiseProxy(method, arguments),
          ...buildCachePromiseProxy(cacheKey, method, arguments, isLocal ? 'local' : 'session')
        }
      }
      continue
    }
    // 不为null时说明在调用cache前调用了其他扩展方法，此时诸如post，get方法的返回值做合并，防止扩展方法丢失。
    const originMethod = cacheAxiosInstance[method]
    cacheAxiosInstance[method] = function () {
      const request = originMethod()
      Object.assign(request, {
        ...buildBasePromiseProxy(method, arguments),
        ...buildCachePromiseProxy(cacheKey, method, arguments, isLocal ? 'local' : 'session')
      })
      return request
    }
  }
  // 添加扩展方法
  for (const key in extendsMethods) {
    cacheAxiosInstance[key] = extendsMethods[key]
  }
  return cacheAxiosInstance
}

/**
 * 扩展方法：开启2FA认证
 * @usage：request.twoFA().[post(), get()...]
 * @returns {{isExtendsAxiosInstance: boolean, post: Function, get: Function, ...}}
 */
extendsMethods.twoFA = function (props) {
  // 打开2FA窗口
  let $twoFAWindow = null
  // - 只有在为获取到密码时（未保存密码或密码已过期）时才打开2FA窗口
  if (cache.twoFA.getPassword() == null) {
    const TwoFAWindowVue = Vue.extend(TwoFAWindow)
    $twoFAWindow = new TwoFAWindowVue({
      el: document.createElement('div'),
      propsData: props
    })
    document.body.appendChild($twoFAWindow.$el)
  }
  let twofaAxiosInstance = {
    // 标记为axios扩展实例，用于与原生axios作区分
    isExtendsAxiosInstance: true
  }
  if (this.isExtendsAxiosInstance) {
    twofaAxiosInstance = this
  }
  for (const method of requestMethods) {
    if (twofaAxiosInstance[method] == null) {
      twofaAxiosInstance[method] = function () {
        return {
          ...buildBasePromiseProxy(method, arguments),
          ...build2FAPromiseProxy($twoFAWindow)
        }
      }
      continue
    }
    // 不为null时说明在调用twoFA前调用了其他扩展方法，此时诸如post，get方法的返回值做合并，防止扩展方法丢失。
    const originMethod = twofaAxiosInstance[method]
    twofaAxiosInstance[method] = function () {
      const request = originMethod()
      Object.assign(request, {
        ...buildBasePromiseProxy(method, arguments),
        ...build2FAPromiseProxy($twoFAWindow)
      })
      return request
    }
  }
  // 添加扩展方法
  for (const key in extendsMethods) {
    twofaAxiosInstance[key] = extendsMethods[key]
  }
  return twofaAxiosInstance
}

export default extendsMethods
